﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Boggle;

using NUnit.Framework;
using Moq;

namespace Boggle.Tests
{
    /// <summary>
    /// Tests the Game class.
    /// </summary>
    public class TestGame
    {
        /// <summary>
        /// Tests a short scripted game which ends with the time limit expiring.
        /// </summary>
        [Test]
        public void TestScriptedGame()
        {
            var dictionary = new Mock<IBoggleDictionary>(MockBehavior.Strict);
            dictionary.Setup(d => d.IsDefineSupported).Returns(false);
            dictionary.Setup(d => d.IsWord(It.IsAny<String>())).Returns(true);

            var grid = new Mock<IBoggleGrid>(MockBehavior.Strict);
            SetupMockGridForWord(grid, "hello");
            SetupMockGridForWord(grid, "there");
            SetupMockGridForWord(grid, "fishes");
			grid.Setup(g => g.ContainsWord("mice")).Returns<List<Die>>(null);
            grid.Setup(g => g.Shuffle());

            var game = new Game(grid.Object, dictionary.Object);
            Assert.AreEqual(Game.GameState.Setup, game.CurrentState);
            
            game.Start(new DateTime(2011, 1, 1));
            Assert.AreEqual(Game.GameState.InProgress, game.CurrentState);
            Assert.AreEqual(180, game.SecondsLeft);

            game.CurrentWord = "hello";
            game.SubmitWord();
            Assert.AreEqual(1, game.Words.Count);
            Assert.AreEqual("hello", game.Words[0].WordText);

            game.CurrentWord = "there";
            game.SubmitWord();
            Assert.AreEqual(2, game.Words.Count);
            Assert.AreEqual("there", game.Words[1].WordText);
            
            Assert.IsTrue(game.Next(new DateTime(2011, 1, 1, 0, 0, 30)), "Couldn't advance game 30 seconds");
            Assert.AreEqual(Game.GameState.InProgress, game.CurrentState);
            Assert.AreEqual(150, game.SecondsLeft);

			game.CurrentWord = "";
			game.SubmitWord();
			Assert.AreEqual(2, game.Words.Count);

            game.CurrentWord = "fishes";
            game.SubmitWord();
            Assert.AreEqual(3, game.Words.Count);
            Assert.AreEqual("fishes", game.Words[2].WordText);

			game.CurrentWord = "mice";
			game.SubmitWord();
			Assert.AreEqual(4, game.Words.Count);
			Assert.AreEqual("mice", game.Words[3].WordText);
			Assert.AreEqual(Word.Status.NotInGrid, game.Words[3].CurrentStatus);

            game.CurrentWord = "hello";
            game.SubmitWord();
			Assert.AreEqual(4, game.Words.Count);

            Assert.IsTrue(game.Next(new DateTime(2011, 1, 1, 0, 3, 0)));
            Assert.AreEqual(Game.GameState.Ended, game.CurrentState);
            Assert.AreEqual(0, game.SecondsLeft);

            while (game.Next(new DateTime(2011, 1, 1, 0, 3, 30))) { /* Wait for words to be validated. */ }
            game.NewGame();
            Assert.AreEqual(Game.GameState.Setup, game.CurrentState);
        }

		/// <summary>
		/// Tests a short game which ends early.
		/// </summary>
		public void TestScriptedGameEarlyEnd()
		{
			var dictionary = new Mock<IBoggleDictionary>(MockBehavior.Strict);
			dictionary.Setup(d => d.IsDefineSupported).Returns(false);
			dictionary.Setup(d => d.IsWord(It.IsAny<String>())).Returns(true);

			var grid = new Mock<IBoggleGrid>(MockBehavior.Strict);
			SetupMockGridForWord(grid, "tee");
			SetupMockGridForWord(grid, "brain");
			SetupMockGridForWord(grid, "healthy");
			grid.Setup(g => g.Shuffle());

			var game = new Game(grid.Object, dictionary.Object);
			Assert.AreEqual(Game.GameState.Setup, game.CurrentState);

			game.Start(new DateTime(2011, 1, 25, 12, 15, 0));
			Assert.AreEqual(Game.GameState.InProgress, game.CurrentState);
			Assert.AreEqual(180, game.SecondsLeft);

			game.CurrentWord = "tee";
			game.SubmitWord();
			Assert.AreEqual(1, game.Words.Count);
			Assert.AreEqual("tee", game.Words[0].WordText);

			Assert.IsTrue(game.Next(new DateTime(2011, 1, 25, 12, 15, 1)), "Couldn't advance game 1 second");
			Assert.AreEqual(Game.GameState.InProgress, game.CurrentState);
			Assert.AreEqual(179, game.SecondsLeft);

			game.CurrentWord = "brain";
			game.SubmitWord();
			Assert.AreEqual(2, game.Words.Count);
			Assert.AreEqual("brain", game.Words[1].WordText);

			Assert.IsTrue(game.Next(new DateTime(2011, 1, 25, 12, 15, 2)), "Couldn't advance game 1 second");
			Assert.AreEqual(Game.GameState.InProgress, game.CurrentState);
			Assert.AreEqual(150, game.SecondsLeft);

			game.CurrentWord = "healthy";
			game.SubmitWord();
			Assert.AreEqual(3, game.Words.Count);
			Assert.AreEqual("healthy", game.Words[2].WordText);

			Assert.IsTrue(game.Next(new DateTime(2011, 1, 25, 12, 15, 3)), "Couldn't advance game 1 second");
			Assert.AreEqual(Game.GameState.InProgress, game.CurrentState);
			Assert.AreEqual(150, game.SecondsLeft);

			game.EndGame();
			Assert.AreEqual(Game.GameState.Ended, game.CurrentState);
			Assert.AreEqual(0, game.SecondsLeft);

			while (game.Next(new DateTime(2011, 1, 25, 12, 15, 4))) { /* Wait for words to be validated. */ }

			Assert.AreEqual(Game.GameState.Ended, game.CurrentState);
			Assert.AreEqual(8, game.Score);
		}

        /// <summary>
        /// Sets up ContainsWord to function correctly for the specifed word.
        /// </summary>
        /// <param name="grid"></param>
        /// <param name="word"></param>
        private void SetupMockGridForWord(Mock<IBoggleGrid> grid, String word)
        {
            var dice = word.Select(c => new Die(c.ToString())).ToList();
            grid.Setup(g => g.ContainsWord(word)).Returns(dice);
		}

		#region Tests for Setup State

		/// <summary>
		/// Tests the Game class for invalid operations when in the Setup state.
		/// </summary>
		[Test, TestCaseSource("GetSetupStateExceptionsCases"), ExpectedException(typeof(InvalidOperationException))]
		public void TestSetupStateExceptions(Action<Game> op)
		{
			var dictionary = new Mock<IBoggleDictionary>(MockBehavior.Strict);
			var grid = new Mock<IBoggleGrid>(MockBehavior.Strict);
			var game = new Game(grid.Object, dictionary.Object);
			op(game);
		}

		/// <summary>
		/// Provides test cases for the SomeMethod test.
		/// </summary>
		/// <returns>Test cases for the SomeMethod test method.</returns>
		public IEnumerable<ITestCaseData> GetSetupStateExceptionsCases()
		{
			yield return new TestCaseData(new Action<Game>(g => g.Next(new DateTime(2011, 1, 1))));
			yield return new TestCaseData(new Action<Game>(g => g.SubmitWord()));
			yield return new TestCaseData(new Action<Game>(g => g.EndGame()));
            yield return new TestCaseData(new Action<Game>(g => g.NewGame()));
		}

        /// <summary>
        /// Tests that we can't set the dictionary to null during setup state.
        /// </summary>
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void TestSetDictionaryException()
        {
            var dictionary = new Mock<IBoggleDictionary>(MockBehavior.Strict);
            var grid = new Mock<IBoggleGrid>(MockBehavior.Strict);
            var game = new Game(grid.Object, dictionary.Object);
            Assert.AreEqual(Game.GameState.Setup, game.CurrentState);
            game.Dictionary = null; 
        }

        /// <summary>
        /// Tests that we can change the dictionary during setup state.
        /// </summary>
        [Test]
        public void TestSetDictionary()
        {
            var dictionary = new Mock<IBoggleDictionary>(MockBehavior.Strict);
            var grid = new Mock<IBoggleGrid>(MockBehavior.Strict);
            var game = new Game(grid.Object, dictionary.Object);
            Assert.AreEqual(Game.GameState.Setup, game.CurrentState);
            var newDictionary = new Mock<IBoggleDictionary>(MockBehavior.Strict);
            Assert.AreNotEqual(dictionary.Object, newDictionary.Object);
            Assert.AreEqual(dictionary.Object, game.Dictionary);
            game.Dictionary = newDictionary.Object;
            Assert.AreEqual(newDictionary.Object, game.Dictionary);
        }

		#endregion

		#region Tests for InProgress State

		/// <summary>
		/// Tests the Game class for invalid operations when in the Ended state.
		/// </summary>
		[Test, TestCaseSource("GetInProgressStateExceptionsCases"), ExpectedException(typeof(InvalidOperationException))]
		public void TestInProgressStateExceptions(Action<Game> op)
		{
			var dictionary = new Mock<IBoggleDictionary>(MockBehavior.Strict);
			var grid = new Mock<IBoggleGrid>(MockBehavior.Strict);
			grid.Setup(g => g.Shuffle());
			var game = new Game(grid.Object, dictionary.Object);
			game.Start(new DateTime(2011, 1, 1));
			op(game);
		}

		/// <summary>
		/// Provides test cases for the SomeMethod test.
		/// </summary>
		/// <returns>Test cases for the SomeMethod test method.</returns>
		public IEnumerable<ITestCaseData> GetInProgressStateExceptionsCases()
		{
			yield return new TestCaseData(new Action<Game>(g => g.Start(new DateTime(2011, 1, 1, 0, 0, 1))));
            yield return new TestCaseData(new Action<Game>(g => g.NewGame()));
            yield return new TestCaseData(new Action<Game>(g => g.Dictionary = new Mock<IBoggleDictionary>().Object));
		}

		#endregion

		#region Tests for Ended State

		/// <summary>
		/// Tests the Game class for invalid operations when in the Ended state.
		/// </summary>
		[Test, TestCaseSource("GetEndedStateExceptionsCases"), ExpectedException(typeof(InvalidOperationException))]
		public void TestEndedStateExceptions(Action<Game> op)
		{
			var dictionary = new Mock<IBoggleDictionary>(MockBehavior.Strict);
			var grid = new Mock<IBoggleGrid>(MockBehavior.Strict);
			grid.Setup(g => g.Shuffle());
			var game = new Game(grid.Object, dictionary.Object);
			game.Start(new DateTime(2011, 1, 1));
			game.EndGame();
			op(game);
		}

		/// <summary>
		/// Provides test cases for the SomeMethod test.
		/// </summary>
		/// <returns>Test cases for the SomeMethod test method.</returns>
		public IEnumerable<ITestCaseData> GetEndedStateExceptionsCases()
		{
			yield return new TestCaseData(new Action<Game>(g => g.Start(new DateTime(2011, 1, 1, 0, 0, 1))));
			yield return new TestCaseData(new Action<Game>(g => g.SubmitWord()));
            yield return new TestCaseData(new Action<Game>(g => g.EndGame()));
            yield return new TestCaseData(new Action<Game>(g => g.Dictionary = new Mock<IBoggleDictionary>().Object));
		}

		#endregion

		#region Ctor

		/// <summary>
		/// Tests the Ctor method.
		/// </summary>
		[Test, TestCaseSource("GetCtorCases")]
		public void TestCtor(IBoggleGrid grid, IBoggleDictionary dict)
		{
			var game = new Game(grid, dict);
			Assert.AreEqual(Game.GameState.Setup, game.CurrentState);
			Assert.AreEqual(String.Empty, game.CurrentWord);
			Assert.AreEqual(dict, game.Dictionary);
			Assert.AreEqual(grid, game.Grid);
			Assert.AreEqual(0, game.Score);
			Assert.AreEqual(0d, game.SecondsLeft);
			Assert.AreEqual(null, game.SelectedWord);
			Assert.IsNotNull(game.Words);
			Assert.AreEqual(0, game.Words.Count);
		}

		/// <summary>
		/// Provides test cases for the Ctor test.
		/// </summary>
		/// <returns>Test cases for the Ctor test method.</returns>
		public IEnumerable<ITestCaseData> GetCtorCases()
		{
			yield return new TestCaseData(
				null, 
				new Mock<IBoggleDictionary>(MockBehavior.Strict).Object)
				.Throws(typeof(ArgumentNullException));

			yield return new TestCaseData(
				new Mock<IBoggleGrid>(MockBehavior.Strict).Object, 
				null)
				.Throws(typeof(ArgumentNullException));

			yield return new TestCaseData(
				new Mock<IBoggleGrid>(MockBehavior.Strict).Object, 
				new Mock<IBoggleDictionary>(MockBehavior.Strict).Object);
		}

		#endregion
	}
}
